ハードシングスを引き起こしたHype Driven Development(HDD)
from
弊社のソフトウェア開発の失敗談( ハードシングスへの突入と脱出 の「根の深い技術的負債」を掘り下げる内容になっています)を共有します。この失敗からなにか参考になるものがあれば幸いです。 結局のところサーバーレスとDDDを採用した強い意志があったわけではなく、当時の流行りを踏まえて技術顧問やソリューションアーキテクト、コミュニティの方と話し合って決めたようです。流行りに乗るのはいいことですよね。市場との向き合い方を端的に表現した「Don’t fight the trend」は私が好きな言葉の1つです。 とにかくインターフェイスが多い。
カラム1つを更新する処理において、Domain、Repository、DTOなどのインターフェイスが関わり、時にはlambdaをまたいで処理が行われていた。
複雑性を1つの関数内に閉じ込めておけばいいものを、ただ単に機械的に複数のモジュールに処理をわけているコードが至るところに存在した。コードと向き合って適切に抽象化していないため、単純にさらに複雑になっていた。
リソースのライフサイクルやモジュール間の依存がかなり複雑。
インターフェイスの多さと似ていますが正直わけがわからなかったので、Mermaidを使って適宜図に起こしていました。詳細は省きますが当時10枚前後、以下のようなものをコードを読みながら適当に作っていました。
良かったのはGolangが循環依存を禁止しているので最悪の事態は免れていた点です。Golang、ほんとうにありがとうございました。
https://scrapbox.io/files/64598bf960fcabf837ba2223.png
このような状況ですが、基本ポジティブなので(さらに、みんなで未来を見ていたので)、以下を考えていました。
技術選定時にビジネスドメインにあったアーキテクチャを選定しているのだろうと信じてました。単に私達がサーバーレスやDDDをわかっていないだけだろう。うまく使えていないはずだと。 色々問題はあるが、サービスが流行ったら人を入れて直そう。悪い点ではなくいい点を見よう。
今になって考えるとこれは完全に誤っていました。善管注意義務を怠っていたと言うべきかもしれません。インセンティブを考慮すると難しかった気がしますが、いったん止める決断を声高に叫ぶべきでした。
AWSについての調査
2019年前半にAWS APIGateway、AWS Lambda、AWS DynamoDBをSaaSの開発のメインスタックとして扱うのは不可能と判断しました。以下が理由です。AWSが悪いのではなく、完全に技術選定時のミスです。マーケティングメッセージに踊らされた弊社の失敗で、市場はやはり弱者を狩りとってきます。以降は、病気は友達の気分で付き合っていき、戦略の失敗を戦術で補完する作業に移ることにしました。 AWS DynamoDBはKey-value Storeであり、BtoB SaaSの複雑なデータモデルを扱えるデータストアではない。リレーションが少ない場合は以下の方法でむりやりいけるが、見て分かるとおりアジリティが高いものではない。 Example of modeling relational data in DynamoDB
AWS APIGatewayとAWS Lambdaを使用する前提の場合は、当時使用できるデータストアは現実的にAWS DynamoDBかAWS Elasticsearch(現在のOpensearch)のみだった。AWS Lambdaの並列性とRDSのコネクション数の上限を考えると、当時RDSを利用する選択肢はとれなかった。
DDDについての調査
思想的な面での価値について。すべてのソフトウェアはあるドメインの課題を解決するために存在すると考えています。DDD以前から、SWEはドメインに沿ってDBを設計し、コードの分割方法を考え、実装しています。そのため、DDDに価値があるとするのであれば、その思想よりも具体的なコードの分割の仕方や管理の手法にあると考えています。しかし、後述にあるとおり私達はオープンソースの世界に実績のある実装例を見つけることができませんでした。
原典について。エリック・エヴァンスのドメイン駆動設計を読んで、言葉の定義の曖昧さや複雑さからrobustな手法ではないと判断しました。少ない前提で成り立つ手法ではなく、使用する言語(おそらく、JavaやC#)や事業フェーズや事業ドメインや予算規模やexpertsの存在など、いくつかの前提がハマったときに初めて検討に値する手法だと。採用すると考慮しなければならない変数が増えるだけなので、スタートアップが利用する手法ではないと判断しました。
実装について。残念ながら、DDDに沿って実装されたオープンソースの良質なライブラリ・フレームワーク・プロダクトを見つけることができませんでした。この現実は許容し難いものでした。もしDDDの手法が優れている(少ない前提で成り立つrobustなもの)なら多くのエンタープライズソフトウェアで採用され、進化変容していき、その一部がRuby on Railsのような形でオープンソースとして世に放たれるはずだと考えています。だが、現実は異なっていました。DDDのアップサイドも明確に見えていない中で、参考にできる実績のある実装もなく現実の世界でworkするかどうかわからないものをなぜスタートアップが採用しなければならないのか。この問いが頭から離れませんでした。
私はLinux Kernel、PostgreSQL、Systemd、RubyのMRIなどのソースコードを読んで壊しながらプログラミングを覚えた、かなりオールドスクールなタイプです。そのためこの判断に偏見が入っている可能性はあります。
例えば、DDDのRepositoryの開発を補助するような良質なライブラリは発見できませんでしたが、DDD以外では存在します。Metaが開発した DataLoader です。データ層とアプリケーションをつなぐライブラリで、複数のデータソースのデータをバッチ処理やキャッシュ処理も考慮し非常に効率よく取得できます。500行未満のシンプルなライブラリですが、弊社の新サービスArchではDataLoaderなしではもう考えられないほど活用しています。
いったんここまでの失敗からの学びをまとめます。何かしらがみなさんの心に刺さり、アウトカムの向上につながると幸いです。
基盤機能の技術選定のポリシー
選定の基準は以下としました。前回の失敗を踏まえて、やることを極限まで減らし、なおかつ世の中のトレンドにものることで、異常なまでの開発のベロシティを叩きだしたいという思いが強くこもっています。
利用するサービスを最小限にする
サービスの量が増えるとそれだけ複雑性がまし、統合するためのコードも増えます。できるだけ汎用性の高いサービスを選択肢、サービスを追加するときにはADRを慎重に書き、メリットがあることを確かめてから追加すべきと考えています。
世の中のトレンドにのる
非常に難しいのは理解した上で、技術選定者はトレンドに乗ろうと最大限努力すべきです。トレンドにのると、気づかないうちに自分たちを遠くへ連れてってくれます。社内のリソースを一切使わずに、使っているサービスやライブラリが急速に進化していきます。逆にトレンドにのれないと、最悪自信でメンテンナスする必要が発生したり、開発者が寄り付かなくなったりします。市場との向き合い方を端的に表現した「Don’t fight the trend」は私が好きな言葉の1つです。
ただ、乗るべきトレンドと乗ってはいけないトレンドが存在します。私は、Hypeサイクルが透けて見える技術は選定しない方針にしています。
フロントエンド・バックエンド・インフラのそれぞれの領域でサイロに閉じこもるのではなく、できるだけ摩擦なくインテグレーションが取れる体制を選びました。また、CDなどは早すぎる最適化と判断しまだ導入していません。手元でコマンドを1つ実行するとデプロイされる、温かみのある手法を採用しています。
5年程度の長期を意識する
どんな技術も廃れていきます。これは自然に摂理です。ただ、Ruby On Railsのように10年以上前線で戦えるものも存在します。私は最低でも5年は前線で耐えてくれるもののみを選択したいと考えています。5年程度耐えてくれたら、Developmentチームはトレンドを考えず顧客をみて開発でき、その間にResearchチームは次の5年耐えれるスタックを調査して実践に投入できるからです。 選定した基盤機能の技術
全体
すべてのコードはGithubの1つのレポジトリで管理する。monorepoにすることで、機能の結合、CI/CD、アップグレード・セキュリティ対応、ドキュメント管理のコストを減らす。
開発で使用する言語をTypeScriptで統一する。プロジェクトやフロントエンド、バックエンド、インフラ問わず、TypeScriptを積極的に利用する。TSConfigやESLintは非常に厳しくし、すべてのプロジェクトで同じ設定のもと運用する。
GCPを積極的に活用する。今後5年AWSは使わない。GCPにないものに限って特定のドメインに特化したサービスを利用する。
GKEとBigQueryとVertex AIに明らかに優位性があるので、他のプロバイダは全体でみて5年は追いつけないと考えた(他のプロバイダを使う場合は、GCPとのマルチクラウドにする必要があると考えた)。思考の対象から外した。
コンピューティング基盤として、GKE Autopilotのみを使用しCloud RunやCloud Functionsは積極的に使用しない。
Nodeの運用は行わない。デプロイ時にPodの割り当てに時間がかかる問題はBlue/Greenの運用でカバーする。スパイクする場合はBalloon Podsを利用する。
バックエンド
PostgreSQLを積極的に活用する。OLTPデータベースとしてのみならず、Job Queueing、Pub/Sub、Cronとしても利用する。Redis、Cloud Pub/Sub、Cloud Schedulerは利用しない。
フロントエンドとバックエンドの通信はすべてGraphQLを使用する。スキーマ駆動を徹底し、GraphQL Code Generator、GraphQL Tools、React Queryを利用してスキーマから型と実装を生成する。 フロントエンド
開発効率を考慮し、フロントエンドのすべてのアプリは同じ構成に統一されている。バックエンドはフロントエンド毎にアプリを分けず1つのアプリで構成されている。
フロントエンドのDev ServerとProxy Server用途に、Viteを採用している。セキュリティ関連のヘッダーからバックエンドの通信まで、本番環境と全く同じ状態で開発ができる体制になっている。 フロントエンドのUIはすべて、ReactとMUIを使い構成する。Figma上のUIデザインもMUI前提に構成されている。MUIには引き続き投資し、MUI X Pro Planの検討も視野に入れている。